/*
 * FocalRange.java
 *
 * Created on February 20, 2005, 3:53 PM
 */

package edu.temple.GUS.MMA.Functions;

import edu.temple.GUS.MMA.DataTypes.*;
import edu.temple.GUS.MMA.Neighborhoods.*;
import edu.temple.GUS.MMA.Exceptions.*;

/**
 *
 * @author  jeffleon
 */
public class FocalRange {
    
    private FocalCalculations focalCalc = new FocalCalculations();
    
    /** Creates a new instance of FocalRange */
    public FocalRange() {}
    
    // I. Functions for single neighborhood and a single lag
    
    public MMATimeSeries execute(MMATimeSeries tSeries, TNeighborhood neighborhood, TLag lag) throws MMAFunctionInitException
    {
        if (tSeries == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time series is not initialized.");
        
        if (neighborhood == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time neighborhood is not initialized.");

        if (lag == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag is not initialized.");

        // Initialze the t dimension
        int t = tSeries.getT();
        float noData = tSeries.getNoData();

        // Output variables
        MMATimeSeries outputTSeries = new MMATimeSeries(t);
        int tOffset = lag.getT();
        float resultData[] = new float[t];
        
        for ( int i=0; i <t; i++)
        {
            
            // T-dimensional boundaries
            int tLowerBoundaryIndex = neighborhood.getLowerBoundary() + tOffset;
            int tUpperBoundaryIndex = neighborhood.getUpperBoundary() + tOffset;
            
            resultData[i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, tSeries, i, tLowerBoundaryIndex, tUpperBoundaryIndex);
        }
        
        outputTSeries.putData(resultData);
        outputTSeries.putNoData(noData);
        return outputTSeries;
    }

    public MMAGrid execute(MMAGrid grid, XYNeighborhood hood, XYLag lag) throws MMAFunctionInitException, MMAException
    {
        if (grid == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time series is not initialized.");
        
        if (hood == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " The neighborhood is not initialized.");

        if (lag == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag is not initialized.");

        // Initialze the x and y dimensions
        int x = grid.getX();
        int y = grid.getY();
        float noData = grid.getNoData();

        // Output variables
        MMAGrid outputGrid = new MMAGrid(x,y);
        int xOffset = lag.getX();
        int yOffset = lag.getY();
        float resultData[][] = new float[y][x];
        
        for ( int j=0; j <y; j++)
            for ( int i=0; i <x; i++)
            {
                // Perform focal calculation for rectanglular neighborhood
                if (XYRectangle.class.isInstance(hood))
                    resultData[j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, grid, i+xOffset, j+yOffset, (XYRectangle)hood);

                // Perform focal calculation for circular neighborhood
                else if (XYCircle.class.isInstance(hood)) 
                    resultData[j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, grid, i+xOffset, j+yOffset, (XYCircle)hood);

                // Perform focal calculation for customed neighborhood
                else if (XYCustomNeighborhood.class.isInstance(hood)) 
                    resultData[j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, grid, i+xOffset, j+yOffset, (XYCustomNeighborhood)hood);

                // Return an error for focal calculation with other unsupported neighborhoods
                else
                    throw new MMAException(this.getClass().toString() + " Unsupported calculation with the given neighborhood");
            }
        
        outputGrid.putData(resultData);
        outputGrid.putNoData(noData);
        return outputGrid;
    }
    
    public MMATimeCube execute(MMATimeCube cube, XYTNeighborhood hood, XYTLag lag) throws MMAFunctionInitException, MMAException
    {
        if (cube == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time cube is not initialized.");
        
        if (hood == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " The neighborhood is not initialized.");

        if (lag == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag is not initialized.");

        // Initialze the x, y and t dimensions
        int x = cube.getX();
        int y = cube.getY();
        int t = cube.getT();
        float noData = cube.getNoData();

        // Output variables
        MMATimeCube outputCube = new MMATimeCube(x,y,t);
        int xOffset = lag.getX();
        int yOffset = lag.getY();
        int tOffset = lag.getT();
        float resultData[][][] = new float[t][y][x];
        
        for ( int k=0; k <t; k++)
            for ( int j=0; j <y; j++)
                for ( int i=0; i <x; i++)
                {
                    // Perform focal calculation for rectanglular neighborhood
                    if (XYTRectangle.class.isInstance(hood))
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+tOffset, (XYTRectangle)hood);

                    // Perform focal calculation for circular neighborhood
                    else if (XYTCircle.class.isInstance(hood)) 
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+tOffset, (XYTCircle)hood);

                    // Perform focal calculation for custom neighborhood
                    else if (XYTCustomNeighborhood.class.isInstance(hood)) 
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+tOffset, (XYTCustomNeighborhood)hood);

                    // Return an error for focal calculation with other unsupported neighborhoods
                    else
                        throw new MMAException(this.getClass().toString() + "Unsupported calculation with the neighborhood");
                }
        
        outputCube.putData(resultData);
        outputCube.putNoData(noData);
        return outputCube;
    }

    public MMASpaceCube execute(MMASpaceCube cube, XYZNeighborhood hood, XYZLag lag) throws MMAFunctionInitException, MMAException
    {
        if (cube == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Space cube is not initialized.");
        
        if (hood == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " The neighborhood is not initialized.");

        if (lag == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag is not initialized.");

        // Initialze the x, y and z dimensions
        int x = cube.getX();
        int y = cube.getY();
        int z = cube.getZ();
        float noData = cube.getNoData();

        // Output variables
        MMASpaceCube outputCube = new MMASpaceCube(x,y,z);
        int xOffset = lag.getX();
        int yOffset = lag.getY();
        int zOffset = lag.getZ();
        float resultData[][][] = new float[z][y][x];
        
        for ( int k=0; k <z; k++)
            for ( int j=0; j <y; j++)
                for ( int i=0; i <x; i++)
                {
                    // Perform focal calculation for rectanglular neighborhood
                    if (XYZRectangle.class.isInstance(hood))
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, (XYZRectangle)hood);

                    // Perform focal calculation for circular neighborhood
                    else if (XYZCircle.class.isInstance(hood)) 
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, (XYZCircle)hood);

                    // Perform focal calculation for customed neighborhood
                    else if (XYZCustomNeighborhood.class.isInstance(hood)) 
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, (XYZCustomNeighborhood)hood);

                    // Return an error for focal calculation with other unsupported neighborhoods
                    else
                        throw new MMAException(this.getClass().toString() + "Unsupported calculation with the neighborhood.");
                }
        
        outputCube.putData(resultData);
        outputCube.putNoData(noData);
        return outputCube;
    }

    public MMAHyperCube execute(MMAHyperCube cube, XYZTNeighborhood hood, XYZTLag lag) throws MMAFunctionInitException, MMAException
    {
        if (cube == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Hyper cube is not initialized.");
        
        if (hood == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " The neighborhood is not initialized.");

        if (lag == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag is not initialized.");

        // Initialze the x, y and z dimensions
        int x = cube.getX();
        int y = cube.getY();
        int z = cube.getZ();
        int t = cube.getT();
        float noData = cube.getNoData();

        // Output variables
        MMAHyperCube outputCube = new MMAHyperCube(x,y,z,t);
        int xOffset = lag.getX();
        int yOffset = lag.getY();
        int zOffset = lag.getZ();
        int tOffset = lag.getT();
        float resultData[][][][] = new float[t][z][y][x];
        
        for ( int l=0; l <t; l++)
            for ( int k=0; k <z; k++)
                for ( int j=0; j <y; j++)
                    for ( int i=0; i <x; i++)
                    {
                        // Perform focal calculation for rectanglular neighborhood
                        if (XYZTRectangle.class.isInstance(hood))
                            resultData[l][k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, l+tOffset, (XYZTRectangle)hood);

                        // Perform focal calculation for circular neighborhood
                        else if (XYZTCircle.class.isInstance(hood)) 
                            resultData[l][k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, l+tOffset, (XYZTCircle)hood);

                        // Perform focal calculation for custom neighborhood
                        else if (XYZTCircle.class.isInstance(hood)) 
                            resultData[l][k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, l+tOffset, (XYZTCustomNeighborhood)hood);

                        // Return an error for focal calculation with other unsupported neighborhoods
                        else
                            throw new MMAException(this.getClass().toString() + "Unsupported calculation with the neighborhood.");

                    }
        
        outputCube.putData(resultData);
        outputCube.putNoData(noData);
         return outputCube;
    }
  
    // II. Functions that take a series of neighborhoods as parameter

    public MMATimeSeries execute(MMATimeSeries tSeries, TNeighborhoodList neighborhoodList, TLag lag) throws MMAFunctionInitException
    {
        if (tSeries == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time series is not initialized.");
        
        if (neighborhoodList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time neighborhood list is not initialized.");

        // Make sure the list is not empty
        if (neighborhoodList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time neighborhood list is empty");
       
        if (lag == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag is not initialized.");

        // Initialze the t dimension
        int t = tSeries.getT();
        float noData = tSeries.getNoData();

        // Ensure the number of time series elements equals to the number of neighborhoods
        if (neighborhoodList.size() != t)
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of timeseries element(s) don't match the number of neighborhood(s).");
        
        // Temp variable
        TNeighborhood currentNeighborhood;

        // Output variables
        MMATimeSeries outputTSeries = new MMATimeSeries(t);
        int tOffset = lag.getT();
        float resultData[] = new float[t];
        
        for ( int i=0; i <t; i++)
        {
            
            // Obtain the current neighborhood
            currentNeighborhood = (TNeighborhood)neighborhoodList.get(i);
            
            // T-dimensional boundaries
            int tLowerBoundaryIndex = currentNeighborhood.getLowerBoundary() + tOffset;
            int tUpperBoundaryIndex = currentNeighborhood.getUpperBoundary() + tOffset;
            
            // Perform the focal calculation for the current element at index i
            resultData[i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, tSeries, i, tLowerBoundaryIndex, tUpperBoundaryIndex);
        }
        
        outputTSeries.putData(resultData);
        outputTSeries.putNoData(noData);
        return outputTSeries;
    }

    public MMAGrid execute(MMAGrid grid, XYNeighborhoodList neighborhoodList, XYLag lag) throws MMAException, MMAFunctionInitException
    {
        if (grid == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time series is not initialized.");
        
        if (neighborhoodList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " XY neighborhood list is not initialized.");

        // Make sure the list is not empty
        if (neighborhoodList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " XY neighborhood list is empty");
       
        if (lag == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag is not initialized.");

        // Initialze the x and y dimensions
        int x = grid.getX();
        int y = grid.getY();
        float noData = grid.getNoData();

        // Ensure the number of the grid elements equals to the number of neighborhoods
        if (neighborhoodList.size() != (x*y))
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of grid elements don't match the number of neighborhood.");
        
        // Temp variable
        int index=0;
        XYNeighborhood currentNeighborhood;

        // Output variables
        MMAGrid outputGrid = new MMAGrid(x,y);
        int xOffset = lag.getX();
        int yOffset = lag.getY();
        float resultData[][] = new float[y][x];
        
        for ( int j=0; j <y; j++)
            for ( int i=0; i <x; i++)
            {
                // Obtain the current neighborhood
                currentNeighborhood = (XYNeighborhood)neighborhoodList.get(index);
                
                // Perform focal calculation for rectanglular neighborhood
                if (XYRectangle.class.isInstance(currentNeighborhood))
                    resultData[j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, grid, i+xOffset, j+yOffset, (XYRectangle)currentNeighborhood);

                // Perform focal calculation for circular neighborhood
                else if (XYCircle.class.isInstance(currentNeighborhood)) 
                    resultData[j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, grid, i+xOffset, j+yOffset, (XYCircle)currentNeighborhood);
                
                // Return an error for focal calculation with other unsupported neighborhoods
                else
                    throw new MMAException(this.getClass().toString() + "Unsupported calculation with neighborhood at position: " + index);

                index++;
            }
        
        outputGrid.putData(resultData);
        outputGrid.putNoData(noData);
        return outputGrid;
    }

    public MMATimeCube execute(MMATimeCube cube, XYTNeighborhoodList neighborhoodList, XYTLag lag) throws MMAException, MMAFunctionInitException
    {
        if (cube == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time cube is not initialized.");
        
        if (neighborhoodList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYT neighborhood list is not initialized.");

        // Make sure the list is not empty
        if (neighborhoodList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYT neighborhood list is empty");
       
        if (lag == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag is not initialized.");

        // Initialze the x, y and t dimensions
        int x = cube.getX();
        int y = cube.getY();
        int t = cube.getT();
        float noData = cube.getNoData();

        // Ensure the number of the cube elements equals to the number of neighborhoods
        if (neighborhoodList.size() != (x*y*t))
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of time cube elements don't match the number of neighborhood.");
        
        // Temp variable
        int index=0;
        XYTNeighborhood currentNeighborhood;

        // Output variables
        MMATimeCube outputCube = new MMATimeCube(x,y,t);
        int xOffset = lag.getX();
        int yOffset = lag.getY();
        int tOffset = lag.getT();
        float resultData[][][] = new float[t][y][x];
        
        for ( int k=0; k <t; k++)
            for ( int j=0; j <y; j++)
                for ( int i=0; i <x; i++)
                {
                    // Obtain the current neighborhood
                    currentNeighborhood = (XYTNeighborhood)neighborhoodList.get(index);

                    // Perform focal calculation for rectanglular neighborhood
                    if (XYTRectangle.class.isInstance(currentNeighborhood))
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+tOffset, (XYTRectangle)currentNeighborhood);

                    // Perform focal calculation for circular neighborhood
                    else if (XYTCircle.class.isInstance(currentNeighborhood)) 
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+tOffset, (XYTCircle)currentNeighborhood);

                    // Return an error for focal calculation with other unsupported neighborhoods
                    else
                        throw new MMAException(this.getClass().toString() + "Unsupported calculation with neighborhood at position: " + index);

                    index++;
                }
        
        outputCube.putData(resultData);
        outputCube.putNoData(noData);
        return outputCube;
    }

    public MMASpaceCube execute(MMASpaceCube cube, XYZNeighborhoodList neighborhoodList, XYZLag lag) throws MMAException, MMAFunctionInitException
    {
        if (cube == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Space cube is not initialized.");
        
        if (neighborhoodList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYZ neighborhood list is not initialized.");

        // Make sure the list is not empty
        if (neighborhoodList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYZ neighborhood list is empty");
       
        if (lag == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag is not initialized.");

        // Initialze the x, y and z dimensions
        int x = cube.getX();
        int y = cube.getY();
        int z = cube.getZ();
        float noData = cube.getNoData();

        // Ensure the number of the cube elements equals to the number of neighborhoods
        if (neighborhoodList.size() != (x*y*z))
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of space cube elements don't match the number of neighborhood.");
        
        // Temp variable
        int index=0;
        XYZNeighborhood currentNeighborhood;

        // Output variables
        MMASpaceCube outputCube = new MMASpaceCube(x,y,z);
        int xOffset = lag.getX();
        int yOffset = lag.getY();
        int zOffset = lag.getZ();
        float resultData[][][] = new float[z][y][x];
        
        for ( int k=0; k <z; k++)
            for ( int j=0; j <y; j++)
                for ( int i=0; i <x; i++)
                {
                    // Obtain the current neighborhood
                    currentNeighborhood = (XYZNeighborhood)neighborhoodList.get(index);

                    // Perform focal calculation for rectanglular neighborhood
                    if (XYZRectangle.class.isInstance(currentNeighborhood))
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, (XYZRectangle)currentNeighborhood);

                    // Perform focal calculation for circular neighborhood
                    else if (XYZCircle.class.isInstance(currentNeighborhood)) 
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, (XYZCircle)currentNeighborhood);

                    // Return an error for focal calculation with other unsupported neighborhoods
                    else
                        throw new MMAException(this.getClass().toString() + "Unsupported calculation with neighborhood at position: " + index);

                    index++;
                }
        
        outputCube.putData(resultData);
        outputCube.putNoData(noData);
        return outputCube;
    }

    public MMAHyperCube execute(MMAHyperCube cube, XYZTNeighborhoodList neighborhoodList, XYZTLag lag) throws MMAException, MMAFunctionInitException
    {
        if (cube == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Hyper cube is not initialized.");
        
        if (neighborhoodList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYZT neighborhood list is not initialized.");

        // Make sure the list is not empty
        if (neighborhoodList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYZT neighborhood list is empty");
       
        if (lag == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag is not initialized.");

        // Initialze the x, y and z dimensions
        int x = cube.getX();
        int y = cube.getY();
        int z = cube.getZ();
        int t = cube.getT();
        float noData = cube.getNoData();

        // Ensure the number of the hyper cube elements equals to the number of xyzt neighborhoods
        if (neighborhoodList.size() != (x*y*z*t))
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of space cube elements don't match the number of neighborhood.");
        
        // Temp variable
        int index=0;
        XYZTNeighborhood currentNeighborhood;

        // Output variables
        MMAHyperCube outputCube = new MMAHyperCube(x,y,z,t);
        int xOffset = lag.getX();
        int yOffset = lag.getY();
        int zOffset = lag.getZ();
        int tOffset = lag.getT();
        float resultData[][][][] = new float[t][z][y][x];
        
        for ( int l=0; l <t; l++)
            for ( int k=0; k <z; k++)
                for ( int j=0; j <y; j++)
                    for ( int i=0; i <x; i++)
                    {
                        // Obtain the current neighborhood
                        currentNeighborhood = (XYZTNeighborhood)neighborhoodList.get(index);

                        // Perform focal calculation for rectanglular neighborhood
                        if (XYZTRectangle.class.isInstance(currentNeighborhood))
                            resultData[l][k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, l+tOffset, (XYZTRectangle)currentNeighborhood);

                        // Perform focal calculation for circular neighborhood
                        else if (XYZTCircle.class.isInstance(currentNeighborhood)) 
                            resultData[l][k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, l+tOffset, (XYZTCircle)currentNeighborhood);

                        // Return an error for focal calculation with other unsupported neighborhoods
                        else
                            throw new MMAException(this.getClass().toString() + "Unsupported calculation with neighborhood at position: " + index);

                        index++;
                    }
        
        outputCube.putData(resultData);
        outputCube.putNoData(noData);
         return outputCube;
    }

    // III. Functions that takes a list of lags as parameter

    public MMATimeSeries execute(MMATimeSeries tSeries, TNeighborhood neighborhood, TLagList lagList) throws MMAFunctionInitException
    {
        if (tSeries == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time series is not initialized.");
        
        if (neighborhood == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time neighborhood is not initialized.");

        if (lagList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is not initialized.");

        // Make sure the list is not empty
        if (lagList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is empty.");
       
        // Initialze the t dimension
        int t = tSeries.getT();
        float noData = tSeries.getNoData();

        // Ensure the number of time series elements equals to the number of lags
        if (lagList.size() != t)
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of timeseries element(s) don't match the number of neighborhood(s).");
        
        // Temp variable
        TLag currentLag;

        // Output variables
        MMATimeSeries outputTSeries = new MMATimeSeries(t);
        int tOffset;
        float resultData[] = new float[t];
        
        for ( int i=0; i <t; i++)
        {
            
            // Obtain the current time lag
            currentLag = lagList.get(i);
            tOffset = currentLag.getT();

            
            // T-dimensional boundaries
            int tLowerBoundaryIndex = neighborhood.getLowerBoundary() + tOffset;
            int tUpperBoundaryIndex = neighborhood.getUpperBoundary() + tOffset;
            
            resultData[i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, tSeries, i, tLowerBoundaryIndex, tUpperBoundaryIndex);

        }
        
        outputTSeries.putData(resultData);
        outputTSeries.putNoData(noData);
        return outputTSeries;
    }

    public MMAGrid execute(MMAGrid grid, XYNeighborhood hood, XYLagList lagList) throws MMAException, MMAFunctionInitException
    {
        if (grid == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Grid is not initialized.");
        
        if (hood == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " The neighborhood is not initialized.");
       
        if (lagList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is not initialized.");

        // Make sure the list is not empty
        if (lagList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is empty.");

        // Initialze the x and y dimensions
        int x = grid.getX();
        int y = grid.getY();
        float noData = grid.getNoData();

        // Ensure the number of the grid elements equals to the number of neighborhoods
        if (lagList.size() != (x*y))
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of grid elements don't match the number of lags.");
        
        // Temp variable
        int index=0;
        XYLag currentLag;
        int xOffset;
        int yOffset;

        // Output variables
        MMAGrid outputGrid = new MMAGrid(x,y);
        float resultData[][] = new float[y][x];
        
        for ( int j=0; j <y; j++)
            for ( int i=0; i <x; i++)
            {
                // Obtain the current lag
                currentLag = lagList.get(index);
                xOffset = currentLag.getX();
                yOffset = currentLag.getY();
                
                // Perform focal calculation for rectanglular neighborhood
                if (XYRectangle.class.isInstance(hood))
                    resultData[j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, grid, i+xOffset, j+yOffset, (XYRectangle)hood);

                // Perform focal calculation for circular neighborhood
                else if (XYCircle.class.isInstance(hood)) 
                    resultData[j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, grid, i+xOffset, j+yOffset, (XYCircle)hood);

                // Perform focal calculation for customed neighborhood
                else if (XYCustomNeighborhood.class.isInstance(hood)) 
                    resultData[j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, grid, i+xOffset, j+yOffset, (XYCustomNeighborhood)hood);

                // Return an error for focal calculation with other unsupported neighborhoods
                else
                    throw new MMAException(this.getClass().toString() + "Unsupported calculation with neighborhood at position: " + index);

                index++;
            }
        
        outputGrid.putData(resultData);
        outputGrid.putNoData(noData);
        return outputGrid;
    }

    public MMATimeCube execute(MMATimeCube cube, XYTNeighborhood hood, XYTLagList lagList) throws MMAException, MMAFunctionInitException
    {
        if (cube == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time cube is not initialized.");
        
        if (hood == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " The neighborhood is not initialized.");

        if (lagList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is not initialized.");

        // Make sure the list is not empty
        if (lagList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is empty.");

        // Initialze the x, y and t dimensions
        int x = cube.getX();
        int y = cube.getY();
        int t = cube.getT();
        float noData = cube.getNoData();

        // Ensure the number of the cube elements equals to the number of neighborhoods
        if (lagList.size() != (x*y*t))
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of time cube elements don't match the number of lags.");
        
        // Temp variable
        int index=0;
        int xOffset;
        int yOffset;
        int tOffset;
        XYTLag currentLag;

        // Output variables
        MMATimeCube outputCube = new MMATimeCube(x,y,t);
        float resultData[][][] = new float[t][y][x];
        
        for ( int k=0; k <t; k++)
            for ( int j=0; j <y; j++)
                for ( int i=0; i <x; i++)
                {
                    // Obtain the current lag
                    currentLag = lagList.get(index);
                    xOffset = currentLag.getX();
                    yOffset = currentLag.getY();
                    tOffset = currentLag.getT();

                    // Perform focal calculation for rectanglular neighborhood
                    if (XYTRectangle.class.isInstance(hood))
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+tOffset, (XYTRectangle)hood);

                    // Perform focal calculation for circular neighborhood
                    else if (XYTCircle.class.isInstance(hood)) 
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+tOffset, (XYTCircle)hood);

                    // Perform focal calculation for custom neighborhood
                    else if (XYTCustomNeighborhood.class.isInstance(hood)) 
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+tOffset, (XYTCustomNeighborhood)hood);

                    // Return an error for focal calculation with other unsupported neighborhoods
                    else
                        throw new MMAException(this.getClass().toString() + "Unsupported calculation with the neighborhood");

                    index++;
                }
        
        outputCube.putData(resultData);
        outputCube.putNoData(noData);
        return outputCube;
    }

    public MMASpaceCube execute(MMASpaceCube cube, XYZNeighborhood hood, XYZLagList lagList) throws MMAException, MMAFunctionInitException
    {
        if (cube == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Space cube is not initialized.");
        
        if (hood == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYZRectangle neighborhood is not initialized.");

        if (lagList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is not initialized.");

        // Make sure the list is not empty
        if (lagList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is empty.");

        // Initialze the x, y and z dimensions
        int x = cube.getX();
        int y = cube.getY();
        int z = cube.getZ();
        float noData = cube.getNoData();

        // Ensure the number of the cube elements equals to the number of lags
        if (lagList.size() != (x*y*z))
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of space cube elements don't match the number of lags.");
        
        // Temp variable
        int index=0;
        int xOffset;
        int yOffset;
        int zOffset;
        XYZLag currentLag;

        // Output variables
        MMASpaceCube outputCube = new MMASpaceCube(x,y,z);
        float resultData[][][] = new float[z][y][x];
        
        for ( int k=0; k <z; k++)
            for ( int j=0; j <y; j++)
                for ( int i=0; i <x; i++)
                {
                    // Obtain the current lag
                    currentLag = lagList.get(index);
                    xOffset = currentLag.getX();
                    yOffset = currentLag.getY();
                    zOffset = currentLag.getZ();

                    // Perform focal calculation for rectanglular neighborhood
                    if (XYZRectangle.class.isInstance(hood))
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, (XYZRectangle)hood);

                    // Perform focal calculation for circular neighborhood
                    else if (XYZCircle.class.isInstance(hood)) 
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, (XYZCircle)hood);

                    // Perform focal calculation for customed neighborhood
                    else if (XYZCustomNeighborhood.class.isInstance(hood)) 
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, (XYZCustomNeighborhood)hood);

                    // Return an error for focal calculation with other unsupported neighborhoods
                    else
                        throw new MMAException(this.getClass().toString() + "Unsupported calculation with the neighborhood.");

                    index++;
                }
        
        outputCube.putData(resultData);
        outputCube.putNoData(noData);
        return outputCube;
    }

    public MMAHyperCube execute(MMAHyperCube cube, XYZTNeighborhood hood, XYZTLagList lagList) throws MMAException, MMAFunctionInitException
    {
        if (cube == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Hyper cube is not initialized.");
        
        if (hood == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYZTRectangle neighborhood is not initialized.");

        if (lagList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is not initialized.");

        // Make sure the list is not empty
        if (lagList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is empty.");

        // Initialze the x, y and z dimensions
        int x = cube.getX();
        int y = cube.getY();
        int z = cube.getZ();
        int t = cube.getT();
        float noData = cube.getNoData();

        // Ensure the number of the hyper cube elements equals to the number of xyzt lags
        if (lagList.size() != (x*y*z*t))
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of space cube elements don't match the number of lags.");
        
        // Temp variable
        int index=0;
        int xOffset;
        int yOffset;
        int zOffset;
        int tOffset;
        XYZTLag currentLag;

        // Output variables
        MMAHyperCube outputCube = new MMAHyperCube(x,y,z,t);
        float resultData[][][][] = new float[t][z][y][x];
        
        for ( int l=0; l <t; l++)
            for ( int k=0; k <z; k++)
                for ( int j=0; j <y; j++)
                    for ( int i=0; i <x; i++)
                    {
                        // Obtain the current lag
                        currentLag = lagList.get(index);
                        xOffset = currentLag.getX();
                        yOffset = currentLag.getY();
                        zOffset = currentLag.getZ();
                        tOffset = currentLag.getT();

                        // Perform focal calculation for rectanglular neighborhood
                        if (XYZTRectangle.class.isInstance(hood))
                            resultData[l][k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, l+tOffset, (XYZTRectangle)hood);

                        // Perform focal calculation for circular neighborhood
                        else if (XYZTCircle.class.isInstance(hood)) 
                            resultData[l][k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, l+tOffset, (XYZTCircle)hood);

                        // Perform focal calculation for custom neighborhood
                        else if (XYZTCircle.class.isInstance(hood)) 
                            resultData[l][k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, l+tOffset, (XYZTCustomNeighborhood)hood);

                        // Return an error for focal calculation with other unsupported neighborhoods
                        else
                            throw new MMAException(this.getClass().toString() + "Unsupported calculation with the neighborhood.");

                        index++;
                    }
        
        outputCube.putData(resultData);
        outputCube.putNoData(noData);
        return outputCube;
    }

    // IV. Functions that take a list of neighborhoods and a list of lags as parameters

    public MMATimeSeries execute(MMATimeSeries tSeries, TNeighborhoodList neighborhoodList, TLagList lagList) throws MMAFunctionInitException
    {
        if (tSeries == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time series is not initialized.");
        
        if (neighborhoodList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time neighborhood list is not initialized.");

        // Make sure the neighborhoodlist is not empty
        if (neighborhoodList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time neighborhood list is empty");
       
        if (lagList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is not initialized.");

        // Make sure the lag list is not empty
        if (lagList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is empty.");
       

        // Initialze the t dimension
        int t = tSeries.getT();
        float noData = tSeries.getNoData();

        // Ensure the number of time series elements equals to the number of neighborhoods
        if (neighborhoodList.size() != t)
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of timeseries element(s) don't match the number of neighborhood(s).");
        
        // Ensure the number of time series elements equals to the number of lags
        if (lagList.size() != t)
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of timeseries element(s) don't match the number of neighborhood(s).");

        // Temp variable
        TNeighborhood currentNeighborhood;
        TLag currentLag;

        // Output variables
        MMATimeSeries outputTSeries = new MMATimeSeries(t);
        int tOffset;
        float resultData[] = new float[t];

        for ( int i=0; i <t; i++)
        {
            
            // Obtain the current neighborhood
            currentNeighborhood = (TNeighborhood)neighborhoodList.get(i);
            
            // Obtain the current time lag
            currentLag = lagList.get(i);

            // T-dimensional boundaries
            tOffset = currentLag.getT();
            int tLowerBoundaryIndex = currentNeighborhood.getLowerBoundary() + tOffset;
            int tUpperBoundaryIndex = currentNeighborhood.getUpperBoundary() + tOffset;
            
            // Perform the focal calculation for the current element at index i
            resultData[i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, tSeries, i, tLowerBoundaryIndex, tUpperBoundaryIndex);
        }
        
        outputTSeries.putData(resultData);
        outputTSeries.putNoData(noData);
        return outputTSeries;
    }

    public MMAGrid execute(MMAGrid grid, XYNeighborhoodList neighborhoodList, XYLagList lagList) throws MMAException, MMAFunctionInitException
    {
        if (grid == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time series is not initialized.");
        
        if (neighborhoodList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " XY neighborhood list is not initialized.");

        // Make sure the neighborhood list is not empty
        if (neighborhoodList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " XY neighborhood list is empty");
       
        if (lagList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is not initialized.");

        // Make sure the lag list is not empty
        if (lagList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is empty.");

        // Initialze the x and y dimensions
        int x = grid.getX();
        int y = grid.getY();
        float noData = grid.getNoData();

        // Ensure the number of the grid elements equals to the number of neighborhoods
        if (neighborhoodList.size() != (x*y))
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of grid elements don't match the number of neighborhood.");
        
        // Temp variable
        int index=0;
        XYNeighborhood currentNeighborhood;
        XYLag currentLag;
        int xOffset;
        int yOffset;

        // Output variables
        MMAGrid outputGrid = new MMAGrid(x,y);
        float resultData[][] = new float[y][x];
        
        for ( int j=0; j <y; j++)
            for ( int i=0; i <x; i++)
            {
                // Obtain the current neighborhood
                currentNeighborhood = (XYNeighborhood)neighborhoodList.get(index);
                
                // Obtain the current lag
                currentLag = lagList.get(index);

                // Define the current x and y offsets
                xOffset = currentLag.getX();
                yOffset = currentLag.getY();
                
                // Perform focal calculation for rectanglular neighborhood
                if (XYRectangle.class.isInstance(currentNeighborhood))
                    resultData[j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, grid, i+xOffset, j+yOffset, (XYRectangle)currentNeighborhood);

                // Perform focal calculation for circular neighborhood
                else if (XYCircle.class.isInstance(currentNeighborhood)) 
                    resultData[j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, grid, i+xOffset, j+yOffset, (XYCircle)currentNeighborhood);
                
                // Return an error for focal calculation with other unsupported neighborhoods
                else
                    throw new MMAException(this.getClass().toString() + "Unsupported calculation with neighborhood at position: " + index);

                index++;
            }
        
        outputGrid.putData(resultData);
        outputGrid.putNoData(noData);
        return outputGrid;
    }

    public MMATimeCube execute(MMATimeCube cube, XYTNeighborhoodList neighborhoodList, XYTLagList lagList) throws MMAException, MMAFunctionInitException
    {
        if (cube == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Time cube is not initialized.");
        
        if (neighborhoodList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYT neighborhood list is not initialized.");

        // Make sure the neighborhood list is not empty
        if (neighborhoodList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYT neighborhood list is empty");
       
        if (lagList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is not initialized.");

        // Make sure the lag list is not empty
        if (lagList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is empty.");

        // Initialze the x, y and t dimensions
        int x = cube.getX();
        int y = cube.getY();
        int t = cube.getT();
        float noData = cube.getNoData();

        // Ensure the number of the cube elements equals to the number of neighborhoods
        if (neighborhoodList.size() != (x*y*t))
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of time cube elements don't match the number of neighborhood.");
        
        // Temp variable
        int index=0;
        XYTNeighborhood currentNeighborhood;
        XYTLag currentLag;
        int xOffset;
        int yOffset;
        int tOffset;

        // Output variables
        MMATimeCube outputCube = new MMATimeCube(x,y,t);
        float resultData[][][] = new float[t][y][x];
        
        for ( int k=0; k <t; k++)
            for ( int j=0; j <y; j++)
                for ( int i=0; i <x; i++)
                {
                    // Obtain the current neighborhood
                    currentNeighborhood = (XYTNeighborhood)neighborhoodList.get(index);

                    // Obtain the current lag
                    currentLag = lagList.get(index);

                    // Determine the offsets for the current focal cell
                    xOffset = currentLag.getX();
                    yOffset = currentLag.getY();
                    tOffset = currentLag.getT();

                    // Perform focal calculation for rectanglular neighborhood
                    if (XYTRectangle.class.isInstance(currentNeighborhood))
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+tOffset, (XYTRectangle)currentNeighborhood);

                    // Perform focal calculation for circular neighborhood
                    else if (XYTCircle.class.isInstance(currentNeighborhood)) 
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+tOffset, (XYTCircle)currentNeighborhood);

                    // Return an error for focal calculation with other unsupported neighborhoods
                    else
                        throw new MMAException(this.getClass().toString() + "Unsupported calculation with neighborhood at position: " + index);

                    index++;
                }
        
        outputCube.putData(resultData);
        outputCube.putNoData(noData);
        return outputCube;
    }

    public MMASpaceCube execute(MMASpaceCube cube, XYZNeighborhoodList neighborhoodList, XYZLagList lagList) throws MMAException, MMAFunctionInitException
    {
        if (cube == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Space cube is not initialized.");
        
        if (neighborhoodList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYZ neighborhood list is not initialized.");

        // Make sure the neighborhood list is not empty
        if (neighborhoodList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYZ neighborhood list is empty");
       
        if (lagList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is not initialized.");

        // Make sure the lag list is not empty
        if (lagList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is empty.");

        // Initialze the x, y and z dimensions
        int x = cube.getX();
        int y = cube.getY();
        int z = cube.getZ();
        float noData = cube.getNoData();

        // Ensure the number of the cube elements equals to the number of neighborhoods
        if (neighborhoodList.size() != (x*y*z))
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of space cube elements don't match the number of neighborhood.");
        
        // Temp variable
        int index=0;
        XYZNeighborhood currentNeighborhood;
        XYZLag currentLag;
        int xOffset;
        int yOffset;
        int zOffset;

        // Output variables
        MMASpaceCube outputCube = new MMASpaceCube(x,y,z);
        float resultData[][][] = new float[z][y][x];
        
        for ( int k=0; k <z; k++)
            for ( int j=0; j <y; j++)
                for ( int i=0; i <x; i++)
                {
                    // Obtain the current neighborhood
                    currentNeighborhood = (XYZNeighborhood)neighborhoodList.get(index);

                    // Obtain the current lag
                    currentLag = lagList.get(index);

                    // Determine the x, y, z offsets
                    xOffset = currentLag.getX();
                    yOffset = currentLag.getY();
                    zOffset = currentLag.getZ();

                    // Perform focal calculation for rectanglular neighborhood
                    if (XYZRectangle.class.isInstance(currentNeighborhood))
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, (XYZRectangle)currentNeighborhood);

                    // Perform focal calculation for circular neighborhood
                    else if (XYZCircle.class.isInstance(currentNeighborhood)) 
			resultData[k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, (XYZCircle)currentNeighborhood);

                    // Return an error for focal calculation with other unsupported neighborhoods
                    else
                        throw new MMAException(this.getClass().toString() + "Unsupported calculation with neighborhood at position: " + index);

                    index++;
                }
        
        outputCube.putData(resultData);
        outputCube.putNoData(noData);
        return outputCube;
    }

    public MMAHyperCube execute(MMAHyperCube cube, XYZTNeighborhoodList neighborhoodList, XYZTLagList lagList) throws MMAException, MMAFunctionInitException
    {
        if (cube == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Hyper cube is not initialized.");
        
        if (neighborhoodList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYZT neighborhood list is not initialized.");

        // Make sure the neighborhood list is not empty
        if (neighborhoodList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " XYZT neighborhood list is empty");
       
        if (lagList == null)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is not initialized.");

        // Make sure the lag list is not empty
        if (lagList.size() == 0)
            throw new MMAFunctionInitException(this.getClass().toString() + " Lag list is empty.");

        // Initialze the x, y and z dimensions
        int x = cube.getX();
        int y = cube.getY();
        int z = cube.getZ();
        int t = cube.getT();
        float noData = cube.getNoData();

        // Ensure the number of the hyper cube elements equals to the number of xyzt neighborhoods
        if (neighborhoodList.size() != (x*y*z*t))
            throw new MMAFunctionInitException(this.getClass().toString() + " Number of space cube elements don't match the number of neighborhood.");
        
        // Temp variable
        int index=0;
        XYZTNeighborhood currentNeighborhood;
        XYZTLag currentLag;
        int xOffset;
        int yOffset;
        int zOffset;
        int tOffset;

        // Output variables
        MMAHyperCube outputCube = new MMAHyperCube(x,y,z,t);
        float resultData[][][][] = new float[t][z][y][x];
        
        for ( int l=0; l <t; l++)
            for ( int k=0; k <z; k++)
                for ( int j=0; j <y; j++)
                    for ( int i=0; i <x; i++)
                    {
                        // Obtain the current neighborhood
                        currentNeighborhood = (XYZTNeighborhood)neighborhoodList.get(index);

                        // Obtain the current lag
                        currentLag = lagList.get(index);

                        // Determine the x, y, z, t offsets
                        xOffset = currentLag.getX();
                        yOffset = currentLag.getY();
                        zOffset = currentLag.getZ();
                        tOffset = currentLag.getT();

                        // Perform focal calculation for rectanglular neighborhood
                        if (XYZTRectangle.class.isInstance(currentNeighborhood))
                            resultData[l][k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, l+tOffset, (XYZTRectangle)currentNeighborhood);

                        // Perform focal calculation for circular neighborhood
                        else if (XYZTCircle.class.isInstance(currentNeighborhood)) 
                            resultData[l][k][j][i] = focalCalc.execute(FocalCalculations.OPERATOR_RANGE, cube, i+xOffset, j+yOffset, k+zOffset, l+tOffset, (XYZTCircle)currentNeighborhood);

                        // Return an error for focal calculation with other unsupported neighborhoods
                        else
                            throw new MMAException(this.getClass().toString() + "Unsupported calculation with neighborhood at position: " + index);

                        index++;
                    }
        
        outputCube.putData(resultData);
        outputCube.putNoData(noData);
         return outputCube;
    }
}
